/*
 *  VM.cpp
 *  vorpal
 *
 *  Created by rwebb on 25/04/08.
 */

#include "VM.h"
#include "Compiler.h"
#include "VmGroup.h"

namespace Vorpal{

class InstructionStatistics{
public:
	InstructionStatistics() : prev(0xFFffFFff){ }
	~InstructionStatistics(){ 
		Print();
	}
	
	void Record(uint32_t instr){
		if(prev != 0xFFffFFff){
			pair< uint32_t, uint32_t > p(prev, instr);
			if(pairFreq.find(p) == pairFreq.end()){
				pairFreq[p] = 0;
			}
			pairFreq[p] += 1;			
		}
		if(freq.find(instr) == freq.end()){
			freq[instr] = 0;
		}
		freq[instr] += 1;
		prev = instr;
	}
	
	void Print(){
		puts("========= instruction stats ==========");
		for(map< uint32_t, uint32_t>::iterator it = freq.begin(); it != freq.end(); ++it){
			printf("%s %u\n", instructions[it->first].name, it->second);
		}
		for(map< pair<uint32_t, uint32_t>, uint32_t >::iterator it = pairFreq.begin(); it != pairFreq.end(); ++it){
			printf("%s->%s %u\n", instructions[it->first.first].name, instructions[it->first.second].name, it->second);			
		}
		puts("======================================");
	}
private:
	uint32_t prev;
	map< uint32_t, uint32_t> freq;
	map< pair<uint32_t, uint32_t>, uint32_t > pairFreq;
};

VM::VM(Handle<Object> sys, bool registerForThreading) :
sys(sys), receivedKill(false), FalseValue(new Unique("False", false)), TrueValue(new Unique("True", false)), NoneValue(new Unique("None", false)){ 
	AssertInstructionSanity();
	if(registerForThreading){
	   TheGroup().Register(this);
	}
}

void VM::Incoming(const RemoteCall &rc){ 
	//cout << "\nIncoming to " << TheGroup()[this] << " == " << rc.Destination();
	incomingCalls.push_back(rc); 
}

void VM::Outgoing(const RemoteCall &rc){ 
	//cout << "\nOutgoing " << TheGroup()[this] << " to " << rc.Destination();
	outgoingCalls.push_back(rc); 
}
	
void VM::AttachOutgoing(const string &firstProcess, const string &secondProcess, bool attachInParallel){
	int numFound = 0;
	int second = 0, first = 0;
	int num = outgoingCalls.size();
	
	for(int i = num - 1; i >= 0; --i){
		if(numFound == 0 && outgoingCalls[i].Destination() == secondProcess){
			second = i;
			numFound += 1;
			i -= 1;
		}
		if(numFound == 1 && outgoingCalls[i].Destination() == firstProcess){
			first = i;
			numFound += 1;
			break;
		}
	}
	if(numFound == 2){
		if(attachInParallel){
			outgoingCalls[first].AttachParallel( outgoingCalls[second] );
		}
		else{
			outgoingCalls[first].AttachSequential( outgoingCalls[second] );
		}
		outgoingCalls.erase(outgoingCalls.begin() + second);
	}
	else{
		Failure(ERR_EVENT_INVALID_ATTACHMENT);
	}
}

void VM::NextCall(){
	if(incomingCalls.size()){
		deferCurrentEvent = false;
		redirectDest = "";
		if(traceProcess){ cout << "("; }
		RemoteCall &rc = incomingCalls.front();
		if(rc.GetMessage() == MSG_kill){
			receivedKill = true;
			return;
		}
		Handle< Object > handler = sys->Get(msg_receive, this)->Get(rc.GetMessage(), this);
		if(typeid(*handler.Ptr()) == typeid(Code)){
			uint numArgs = rc.NumArgs();
			Handle<Object> args[numArgs];
			loop(i, 0, numArgs){
				istringstream is(rc[i]);
				args[i] = Object::Unserialize(is, this);
			}

			Handle<Code> methodCode = Translate<Code>(handler);
			Handle<Object> result = Execute(methodCode, sys, sys, numArgs, args);
			
			if(rc.Sequential().size() > 0){
				RemoteCall &next = rc.Sequential()[0];
				if(result.Ptr() != sys.Ptr()){
					next.InsertResult(result, this);
					loop(p, 0, next.Parallel().size()){
						next.Parallel()[p].InsertResult(result, this);
					}
				}
				Outgoing(next);
			}
		}
		else{
			Failure(ERR_EVENT_NO_RECEIVE);
		}
		if(redirectDest.length() > 0){
			RemoteCall redirect = rc;
			redirect.Destination(redirectDest);
			Outgoing(redirect);
		}
		if(deferCurrentEvent){
			incomingCalls.push_back(incomingCalls.front());
		}
		incomingCalls.pop_front();
		//cout << "\nbacklog = " << incomingCalls.size() << " ";
		if(traceProcess){ cout << ")"; }
	}
}

static void SendCall(const RemoteCall &rc){
	VM *dest = TheGroup()[rc.Destination()];
	if(dest){
		TheGroup()[rc.Destination()]->Incoming(rc);
	}
	else{
		Failure(ERR_EVENT_DEST_DOES_NOT_EXIST);
	}	
}
	
uint VM::SendCalls(){
	uint result = outgoingCalls.size();
	loop(i, 0, outgoingCalls.size()){
		RemoteCall &rc = outgoingCalls[i];
		if(rc.GetMessage() == MSG_spawn){
			TheGroup().Receive(rc);
		}
		else{
			vector< RemoteCall > par = rc.Parallel();
			rc.ClearParallel();
			SendCall(rc);
			loop(p, 0, par.size()){
				SendCall(par[p]);
				result += 1;
			}
		}
	}
	outgoingCalls.clear();
	return result;
}	
	
#define NEXT_INSTRUCTION  rs.Top().FromIp(); rs.Top().NextIp()

void VM::Execute(Handle<Code> startingCode, uint argsPassed, const Cell *exceptionCode){
	//InstructionStatistics stats;
	uint initialReturnDepth = rs.Depth();
	
	try{
		StartCall(startingCode, argsPassed);
		if(traceOpcodes){ printf("Starting to execute with %u args and %u locals\n", rs.Top().NumParams(), rs.Top().NumLocals()); }
		
		while(true){
			uint32_t opcode = NEXT_INSTRUCTION;
			//stats.Record(opcode);
			if(traceOpcodes){
				puts("--------------");
				cout << rs.Top().DebugSourceFile() << " at Line " << rs.Top().DebugLineNumber() << endl;
				printf("depth = %i : ", Depth() - rs.Top().FirstLocal() - rs.Top().NumLocals());	
				puts(instructions[opcode].name);
				PrintStack();
				puts("--------------");
			}
			switch(opcode){
				case NOP: {
					break;
				}
				case GET: {	// args are at negative indices from start of locals
					int localIndex = NEXT_INSTRUCTION;
					// TODO: Check only once; these checks should not be needed since index, NumParams, and NumLocals are constant
					if(localIndex < 0){ Assert(-localIndex <= int(rs.Top().NumParams()) ); }
					else{				Assert( localIndex  < int(rs.Top().NumLocals()) ); }
					
					Object *target = FromBase(rs.Top().FirstLocal() + localIndex).Ptr();
					if(traceOpcodes){
						printf("Get[%i] = ", localIndex);
						cout << target->ToString();
					}
					PushPtr(target);
					break;
				}
				case SET: {	// args are at negative indices from start of locals
					int localIndex = NEXT_INSTRUCTION;
										
					// TODO: Check only once; these checks should not be needed since index, NumParams, and NumLocals are constant
					if(localIndex < 0){ Assert(-localIndex <= int(rs.Top().NumParams())   ); }
					else{				Assert( localIndex  < int(rs.Top().NumLocals()) ); }
					
					if(traceOpcodes){
						printf("Set[%i] = ", localIndex);
						cout << FromTop(0).Ptr()->ToString();
					}
					int numOnTop = Depth() - rs.Top().FirstLocal() - rs.Top().NumLocals();
					Assert(numOnTop >= 1);
					MoveTopTo(rs.Top().FirstLocal() + localIndex); 
					break;
				}
				case MESSAGE: {	
					uint32_t numArgs = NEXT_INSTRUCTION;
					Message msg = rs.Top().FromOp().as_message;
					rs.Top().NextOp();
					if(rs.Top().FromIp() == RETURN && rs.Top().FromIp(1) == kTailCallMarker){
						uint dest = rs.Top().FirstArg();
						uint src = Depth() - numArgs;
						int numToDrop = Depth() - (dest + numArgs);
						loop(i, 0, numArgs){
							FromBase(dest++) = FromBase(src++);
						}
						Drop(numToDrop);
						rs.Pop();
					}
					if(msg == 0){
						msg = FromTop(numArgs - 1)->Hash();
						RemoveFromTop(numArgs - 1);
					}
					if(!StartStackCall(msg, numArgs) && !FromTop(numArgs - 1)->Receive(msg, numArgs, this)){
						cout << "Unhandled message sent to [" << FromTop(numArgs - 1)->ToString() << "]\n";
						Failure(ERR_INVALID_MESSAGE_PASSED);
					}
					if(rs.Depth() == 0 || rs.Depth() == initialReturnDepth){
						return;
					}
					break;
				}
				case JUMP: {
					Cell operand = rs.Top().FromOp();
					rs.Top().Jump(operand.jump.ip - 1, operand.jump.op);  
					break;
				}
				case JUMP_F: {	
					if( !ToBool(FromTop(0).Ptr()) ){
						Cell operand = rs.Top().FromOp();
						rs.Top().Jump(operand.jump.ip - 1, operand.jump.op);  
					}
					else{
						rs.Top().NextOp();
					}
					Drop(1);
					break;
				}
				case RETURN: {
					int localIndex = NEXT_INSTRUCTION;
					Assert(localIndex != kTailCallMarker);
					if(localIndex < 0){ // basically do a SET instruction
						int numOnTop = Depth() - rs.Top().FirstLocal() - rs.Top().NumLocals();
						Assert(numOnTop >= 1);
						MoveTopTo(rs.Top().FirstLocal() + localIndex); 
					}
					//Assert(context.numArgs >= 0);
					int toDrop = Depth() - rs.Top().FirstLocal() + (rs.Top().NumParams() - 1);
					Drop(toDrop);
					rs.Pop();
					if(rs.Depth() == 0 || rs.Depth() == initialReturnDepth){
						return;
					}
					break;
				}
				case NUM: {								
					PushPtr( NewNumber(rs.Top().FromOp().as_float));
					rs.Top().NextOp();
					break;
				}
				case SMALL: {
					int value = NEXT_INSTRUCTION;
					PushPtr( NewNumber( value ) ); 
					break;
				}
				case STR: {	
					PushPtr( new String((const char *)(rs.Top().OperandPointer() + 1)) ); 
					rs.Top().OffsetOp(rs.Top().OperandPointer()->as_uint);
					break;
				}
				case CODE: {								
					PushPtr( new Code(rs.Top().OperandPointer() + 1) );
					rs.Top().OffsetOp(rs.Top().OperandPointer()->as_uint);
					break;
				}
				case OBJ: {	// ( [proto] -> obj )
					uint32_t numOnTopOfStack = Depth() - rs.Top().FirstLocal() - rs.Top().NumLocals();
					uint32_t numArgs = NEXT_INSTRUCTION;
					Assert(numArgs <= numOnTopOfStack);
					if(numArgs > 0){
						Object *obj = FromTop(numArgs - 1)->Clone();
						FromTop(numArgs - 1) = obj;
						if(!StartStackCall(msg_created, numArgs) && numArgs > 1){
							Failure(ERR_INVALID_NUM_ARGS);
						}
					}
					else{
						Object *obj = new Object();
						PushPtr(obj);
					}
					break;
				}
				case OBJ_SET: {	// ( obj [key] value -> obj )
					int numOnTop = Depth() - rs.Top().FirstLocal() - rs.Top().NumLocals();
					Assert(numOnTop >= 2);
					Message msg = rs.Top().FromOp().as_message;
					rs.Top().NextOp();
					if(msg != 0){ // ( obj value -> ), key is hash value at next[0]
						Object *obj = FromTop(1).Ptr();
						obj->Set(msg, FromTop(0) );
						Drop(1);
					}
					else{ // ( obj key value -> )
						Assert(numOnTop >= 3);
						FromTop(2)->Set(FromTop(1)->Hash(), FromTop(0) );
						Drop(2);
					}
					break;
				}
				case OBJ_GET: { // ( obj [key] -> value )						
					int numOnTop = Depth() - rs.Top().FirstLocal() - rs.Top().NumLocals();
					Assert(numOnTop >= 1);
					Message msg = rs.Top().FromOp().as_message;
					rs.Top().NextOp();
					if(msg != 0){ // ( obj -> value )
						Handle<Object> value = FromTop(0)->Get(msg, this);
						Drop(1);
						Push(value);
					}
					else{ // ( obj key -> value )
						Handle<Object> value = FromTop(1)->Get(FromTop(0)->Hash(), this);
						Drop(2);
						Push(value);
					}
					break;
				}
				case SYMBOL: {	
					Message msg = rs.Top().FromOp().as_message;
					rs.Top().NextOp();
					switch(msg){
						case msg_sys:
							Push(sys); 
							break;
						case msg_none:
							PushNone();
							break;
						case msg_true:
							PushBool(true);
							break;
						case msg_false:
							PushBool(false);
							break;
						case msg_code: {
							Handle<Object> codeObj( rs.TopCode().Ptr() );
							Push(codeObj); 
							break;		
						}
						default:
							Failure(ERR_KEY_MISSING);
					}
					break;
				}
				case DROP: {								
					Drop(1);
					break;
				}
			}
		}
	}
	catch(...){
		int lineNumber =  rs.Top().DebugLineNumber(); //DebugLineNumber is indexing starts at zero
		cout << "Execution error occured at: " << rs.Top().DebugSourceFile() << " line " << lineNumber << endl;
		try {
			FileLineReader source( rs.Top().DebugSourceFile() );
			source.Print( lineNumber );
		}
		catch(...){
			cout << "Use command line switch -r to show more debugging information" << endl;
			throw;
		}
		
		cout << "Stack:" << endl;
		cout << "extra depth = " << Depth() - rs.Top().FirstLocal() - rs.Top().NumLocals() << endl;	
		PrintStack();
		throw;
	}
}

void VM::PrintStack(){
	cout << "\trs: " << rs.Depth() << endl;
	loop(i, 0, Depth()){
		cout << "\t [" << i << "] = " << FromBase(i)->ToString() << endl;
	}
}

bool VM::LoadCode(Handle<Code> &code, const string &path){
	if(loadedCode.find(path) != loadedCode.end()){
		code = loadedCode[path];
		return true;
	}
	else{
		Compiler c;
		if( c.Compile(path.c_str()) ){
			Cell *codeCells = new Cell[c.Size()]; // TODO: never deallocated, has to stay around as long as code objects reference it
			c.Get(codeCells, c.Size());
			code = new Code(codeCells);
			return true;
		}	
	}
	return false;
}

}

